#pragma once
#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <cstring>
#include <functional>
#include <string>
#include "crow/settings.h"
namespace crow {
  namespace spell {
#ifndef CROW_MSVC_WORKAROUND
    struct OutOfRange {
      OutOfRange(unsigned /*pos*/,unsigned /*length*/) {}
    };
    constexpr unsigned requires_in_range(unsigned i,unsigned len) {
      return i>=len?throw OutOfRange(i,len):i;
    }

    /// A constant string implementation.
    class const_str {
      const char * const begin_;
      unsigned size_;

      public:
      template< unsigned N >
      constexpr const_str(const char(&arr)[N]): begin_(arr),size_(N-1) {
        static_assert(N>=1,"not a string literal");
      }
      constexpr char operator[](unsigned i) const {
        return requires_in_range(i,size_),begin_[i];
      }

      constexpr operator const char *() const {
        return begin_;
      }

      constexpr const char* begin() const { return begin_; }
      constexpr const char* end() const { return begin_+size_; }

      constexpr unsigned size() const {
        return size_;
      }
    };

    constexpr unsigned find_closing_tag(const_str s,unsigned p) {
      return s[p]=='>'?p:find_closing_tag(s,p+1);
    }

    constexpr bool is_valid(const_str s,unsigned i=0,int f=0) {
      return
        i==s.size()
        ?f==0:
        f<0||f>=2
        ?false:
        s[i]=='<'
        ?is_valid(s,i+1,f+1):
        s[i]=='>'
        ?is_valid(s,i+1,f-1):
        is_valid(s,i+1,f);
    }

    constexpr bool is_equ_p(const char* a,const char* b,unsigned n) {
      return
        *a==0&&*b==0&&n==0
        ?true:
        (*a==0||*b==0)
        ?false:
        n==0
        ?true:
        *a!=*b
        ?false:
        is_equ_p(a+1,b+1,n-1);
    }

    constexpr bool is_equ_n(const_str a,unsigned ai,const_str b,unsigned bi,unsigned n) {
      return
        ai+n>a.size()||bi+n>b.size()
        ?false:
        n==0
        ?true:
        a[ai]!=b[bi]
        ?false:
        is_equ_n(a,ai+1,b,bi+1,n-1);
    }

    constexpr bool is_int(const_str s,unsigned i) {
      return is_equ_n(s,i,"<int>",0,5);
    }

    constexpr bool is_uint(const_str s,unsigned i) {
      return is_equ_n(s,i,"<uint>",0,6);
    }

    constexpr bool is_float(const_str s,unsigned i) {
      return is_equ_n(s,i,"<float>",0,7)||
        is_equ_n(s,i,"<double>",0,8);
    }

    constexpr bool is_str(const_str s,unsigned i) {
      return is_equ_n(s,i,"<str>",0,5)||
        is_equ_n(s,i,"<string>",0,8);
    }

    constexpr bool is_path(const_str s,unsigned i) {
      return is_equ_n(s,i,"<path>",0,6);
    }
#endif
    template <typename T>
    struct parameter_tag {
      static const int value=0;
    };
#define CROW_INTERNAL_PARAMETER_TAG(t, i) \
template <> \
struct parameter_tag<t> \
{ \
    static const int value = i; \
}
    CROW_INTERNAL_PARAMETER_TAG(int,1);
    CROW_INTERNAL_PARAMETER_TAG(char,1);
    CROW_INTERNAL_PARAMETER_TAG(short,1);
    CROW_INTERNAL_PARAMETER_TAG(long,1);
    CROW_INTERNAL_PARAMETER_TAG(long long,1);
    CROW_INTERNAL_PARAMETER_TAG(unsigned int,2);
    CROW_INTERNAL_PARAMETER_TAG(unsigned char,2);
    CROW_INTERNAL_PARAMETER_TAG(unsigned short,2);
    CROW_INTERNAL_PARAMETER_TAG(unsigned long,2);
    CROW_INTERNAL_PARAMETER_TAG(unsigned long long,2);
    CROW_INTERNAL_PARAMETER_TAG(double,3);
    CROW_INTERNAL_PARAMETER_TAG(std::string,4);
#undef CROW_INTERNAL_PARAMETER_TAG
    template <typename ... Args>
    struct compute_parameter_tag_from_args_list;

    template <>
    struct compute_parameter_tag_from_args_list<> {
      static const int value=0;
    };

    template <typename Arg,typename ... Args>
    struct compute_parameter_tag_from_args_list<Arg,Args...> {
      static const int sub_value=
        compute_parameter_tag_from_args_list<Args...>::value;
      static const int value=
        parameter_tag<typename std::decay<Arg>::type>::value
        ?sub_value*6+parameter_tag<typename std::decay<Arg>::type>::value
        :sub_value;
    };

    static inline bool is_parameter_tag_compatible(uint64_t a,uint64_t b) {
      if (a==0)
        return b==0;
      if (b==0)
        return a==0;
      int sa=a%6;
      int sb=a%6;
      if (sa==5) sa=4;
      if (sb==5) sb=4;
      if (sa!=sb)
        return false;
      return is_parameter_tag_compatible(a/6,b/6);
    }

    static inline unsigned find_closing_tag_runtime(const char* s,unsigned p) {
      return
        s[p]==0
        ?throw std::runtime_error("unmatched tag <"):
        s[p]=='>'
        ?p:find_closing_tag_runtime(s,p+1);
    }

    static inline uint64_t get_parameter_tag_runtime(const char* s,unsigned p=0) {
      return
        s[p]==0
        ?0:
        s[p]=='<'?(
          std::strncmp(s+p,"<int>",5)==0
          ?get_parameter_tag_runtime(s,find_closing_tag_runtime(s,p))*6+1:
          std::strncmp(s+p,"<uint>",6)==0
          ?get_parameter_tag_runtime(s,find_closing_tag_runtime(s,p))*6+2:
          (std::strncmp(s+p,"<float>",7)==0||
           std::strncmp(s+p,"<double>",8)==0)
          ?get_parameter_tag_runtime(s,find_closing_tag_runtime(s,p))*6+3:
          (std::strncmp(s+p,"<str>",5)==0||
           std::strncmp(s+p,"<string>",8)==0)
          ?get_parameter_tag_runtime(s,find_closing_tag_runtime(s,p))*6+4:
          std::strncmp(s+p,"<path>",6)==0
          ?get_parameter_tag_runtime(s,find_closing_tag_runtime(s,p))*6+5:
          throw std::runtime_error("invalid parameter type")
          ):
        get_parameter_tag_runtime(s,p+1);
    }
#ifndef CROW_MSVC_WORKAROUND
    constexpr uint64_t get_parameter_tag(const_str s,unsigned p=0) {
      return
        p==s.size()
        ?0:
        s[p]=='<'?(
          is_int(s,p)
          ?get_parameter_tag(s,find_closing_tag(s,p))*6+1:
          is_uint(s,p)
          ?get_parameter_tag(s,find_closing_tag(s,p))*6+2:
          is_float(s,p)
          ?get_parameter_tag(s,find_closing_tag(s,p))*6+3:
          is_str(s,p)
          ?get_parameter_tag(s,find_closing_tag(s,p))*6+4:
          is_path(s,p)
          ?get_parameter_tag(s,find_closing_tag(s,p))*6+5:
          throw std::runtime_error("invalid parameter type")
          ):
        get_parameter_tag(s,p+1);
    }
#endif

    template <typename ... T>
    struct S {
      template <typename U>
      using push=S<U,T...>;
      template <typename U>
      using push_back=S<T...,U>;
      template <template<typename ... Args> class U>
      using rebind=U<T...>;
    };
    template <typename F,typename Set>
    struct CallHelper;
    template <typename F,typename ...Args>
    struct CallHelper<F,S<Args...>> {
      template <typename F1,typename ...Args1,typename=
        decltype(std::declval<F1>()(std::declval<Args1>()...))
      >
        static char __test(int);

      template <typename ...>
      static int __test(...);

      static constexpr bool value=sizeof(__test<F,Args...>(0))==sizeof(char);
    };


    template <int N>
    struct single_tag_to_type {};

    template <>
    struct single_tag_to_type<1> {
      using type=int64_t;
    };

    template <>
    struct single_tag_to_type<2> {
      using type=uint64_t;
    };

    template <>
    struct single_tag_to_type<3> {
      using type=double;
    };

    template <>
    struct single_tag_to_type<4> {
      using type=std::string;
    };

    template <>
    struct single_tag_to_type<5> {
      using type=std::string;
    };


    template <uint64_t Tag>
    struct arguments {
      using subarguments=typename arguments<Tag/6>::type;
      using type=
        typename subarguments::template push<typename single_tag_to_type<Tag%6>::type>;
    };

    template <>
    struct arguments<0> {
      using type=S<>;
    };

    template <typename ... T>
    struct last_element_type {
      using type=typename std::tuple_element<sizeof...(T)-1,std::tuple<T...>>::type;
    };


    template <>
    struct last_element_type<> {};


    // from http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth
    template<class T> using Invoke=typename T::type;

    template<unsigned...> struct seq { using type=seq; };

    template<class S1,class S2> struct concat;

    template<unsigned... I1,unsigned... I2>
    struct concat<seq<I1...>,seq<I2...>>
      : seq<I1...,(sizeof...(I1)+I2)...> {};

    template<class S1,class S2>
    using Concat=Invoke<concat<S1,S2>>;

    template<unsigned N> struct gen_seq;
    template<unsigned N> using GenSeq=Invoke<gen_seq<N>>;

    template<unsigned N>
    struct gen_seq : Concat<GenSeq<N/2>,GenSeq<N-N/2>> {};

    template<> struct gen_seq<0> : seq<> {};
    template<> struct gen_seq<1> : seq<0> {};

    template <typename Seq,typename Tuple>
    struct pop_back_helper;

    template <unsigned ... N,typename Tuple>
    struct pop_back_helper<seq<N...>,Tuple> {
      template <template <typename ... Args> class U>
      using rebind=U<typename std::tuple_element<N,Tuple>::type...>;
    };

    template <typename ... T>
    struct pop_back //: public pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>
    {
      template <template <typename ... Args> class U>
      using rebind=typename pop_back_helper<typename gen_seq<sizeof...(T)-1>::type,std::tuple<T...>>::template rebind<U>;
    };

    template <>
    struct pop_back<> {
      template <template <typename ... Args> class U>
      using rebind=U<>;
    };

    // from http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type
    template < typename Tp,typename... List >
    struct contains : std::true_type {};

    template < typename Tp,typename Head,typename... Rest >
    struct contains<Tp,Head,Rest...>
      : std::conditional< std::is_same<Tp,Head>::value,
      std::true_type,
      contains<Tp,Rest...>
      >::type {};

    template < typename Tp >
    struct contains<Tp> : std::false_type {};

    template <typename T>
    struct empty_context {};

    template <typename T>
    struct promote {
      using type=T;
    };

#define CROW_INTERNAL_PROMOTE_TYPE(t1, t2) \
        template<> \
        struct promote<t1> \
        {  \
            using type = t2; \
        }

    CROW_INTERNAL_PROMOTE_TYPE(char,int64_t);
    CROW_INTERNAL_PROMOTE_TYPE(short,int64_t);
    CROW_INTERNAL_PROMOTE_TYPE(int,int64_t);
    CROW_INTERNAL_PROMOTE_TYPE(long,int64_t);
    CROW_INTERNAL_PROMOTE_TYPE(long long,int64_t);
    CROW_INTERNAL_PROMOTE_TYPE(unsigned char,uint64_t);
    CROW_INTERNAL_PROMOTE_TYPE(unsigned short,uint64_t);
    CROW_INTERNAL_PROMOTE_TYPE(unsigned int,uint64_t);
    CROW_INTERNAL_PROMOTE_TYPE(unsigned long,uint64_t);
    CROW_INTERNAL_PROMOTE_TYPE(unsigned long long,uint64_t);
    CROW_INTERNAL_PROMOTE_TYPE(float,double);
#undef CROW_INTERNAL_PROMOTE_TYPE

    template <typename T>
    using promote_t=typename promote<T>::type;

  } // namespace black_magic

  namespace detail {

    template <class T,std::size_t N,class... Args>
    struct get_index_of_element_from_tuple_by_type_impl {
      static constexpr auto value=N;
    };

    template <class T,std::size_t N,class... Args>
    struct get_index_of_element_from_tuple_by_type_impl<T,N,T,Args...> {
      static constexpr auto value=N;
    };

    template <class T,std::size_t N,class U,class... Args>
    struct get_index_of_element_from_tuple_by_type_impl<T,N,U,Args...> {
      static constexpr auto value=get_index_of_element_from_tuple_by_type_impl<T,N+1,Args...>::value;
    };

  } // namespace detail

  namespace utility {
    template <class T,class... Args>
    T& get_element_by_type(std::tuple<Args...>& t) {
      return std::get<detail::get_index_of_element_from_tuple_by_type_impl<T,0,Args...>::value>(t);
    }

    template<typename T>
    struct function_traits;

#ifndef CROW_MSVC_WORKAROUND
    template<typename T>
    struct function_traits : public function_traits<decltype(&T::operator())> {
      using parent_t=function_traits<decltype(&T::operator())>;
      static const size_t arity=parent_t::arity;
      using result_type=typename parent_t::result_type;
      template <size_t i>
      using arg=typename parent_t::template arg<i>;

    };
#endif

    template<typename ClassType,typename R,typename ...Args>
    struct function_traits<R(ClassType::*)(Args...) const> {
      static const size_t arity=sizeof...(Args);

      typedef R result_type;

      template <size_t i>
      using arg=typename std::tuple_element<i,std::tuple<Args...>>::type;
    };

    template<typename ClassType,typename R,typename ...Args>
    struct function_traits<R(ClassType::*)(Args...)> {
      static const size_t arity=sizeof...(Args);

      typedef R result_type;

      template <size_t i>
      using arg=typename std::tuple_element<i,std::tuple<Args...>>::type;
    };

    template<typename R,typename ...Args>
    struct function_traits<std::function<R(Args...)>> {
      static const size_t arity=sizeof...(Args);

      typedef R result_type;

      template <size_t i>
      using arg=typename std::tuple_element<i,std::tuple<Args...>>::type;
    };

    inline static std::string base64encode(const char* data,size_t size,const char* key="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") {
      std::string ret;
      ret.resize((size+2)/3*4);
      auto it=ret.begin();
      while (size>=3) {
        *it++=key[(static_cast<unsigned char>(*data)&0xFC)>>2];
        unsigned char h=(static_cast<unsigned char>(*data++)&0x03)<<4;
        *it++=key[h|((static_cast<unsigned char>(*data)&0xF0)>>4)];
        h=(static_cast<unsigned char>(*data++)&0x0F)<<2;
        *it++=key[h|((static_cast<unsigned char>(*data)&0xC0)>>6)];
        *it++=key[static_cast<unsigned char>(*data++)&0x3F];

        size-=3;
      }
      if (size==1) {
        *it++=key[(static_cast<unsigned char>(*data)&0xFC)>>2];
        unsigned char h=(static_cast<unsigned char>(*data++)&0x03)<<4;
        *it++=key[h];
        *it++='=';
        *it++='=';
      } else if (size==2) {
        *it++=key[(static_cast<unsigned char>(*data)&0xFC)>>2];
        unsigned char h=(static_cast<unsigned char>(*data++)&0x03)<<4;
        *it++=key[h|((static_cast<unsigned char>(*data)&0xF0)>>4)];
        h=(static_cast<unsigned char>(*data++)&0x0F)<<2;
        *it++=key[h];
        *it++='=';
      }
      return ret;
    }
    inline static std::string base64encode_urlsafe(const char* data,size_t size) {
      return base64encode(data,size,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
    }
  } // namespace utility
}
